package com.alipay.api.internal.util.json;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 不能直接使用JSONReader，请用JSONValidatingReader，所以这里改为abstract修饰。
 */
public abstract class JSONReader {

	public static final  int                       FIRST      = 0;
	public static final  int                       CURRENT    = 1;
	public static final  int                       NEXT       = 2;
	private static final Object                    OBJECT_END = new Object();
	private static final Object                    ARRAY_END  = new Object();
	private static final Object                    COLON      = new Object();
	private static final Object                    COMMA      = new Object();
	private static       Map<Character, Character> escapes    = new HashMap<Character, Character>();

	static {
		escapes.put(Character.valueOf('"'), Character.valueOf('"'));
		escapes.put(Character.valueOf('\\'), Character.valueOf('\\'));
		escapes.put(Character.valueOf('/'), Character.valueOf('/'));
		escapes.put(Character.valueOf('b'), Character.valueOf('\b'));
		escapes.put(Character.valueOf('f'), Character.valueOf('\f'));
		escapes.put(Character.valueOf('n'), Character.valueOf('\n'));
		escapes.put(Character.valueOf('r'), Character.valueOf('\r'));
		escapes.put(Character.valueOf('t'), Character.valueOf('\t'));
	}

	private CharacterIterator it;
	private char              c;
	private Object            token;
	private StringBuffer buf = new StringBuffer();

	private char next() {
		c = it.next();
		return c;
	}

	private void skipWhiteSpace() {
		while (Character.isWhitespace(c)) {
			next();
		}
	}

	public Object read(CharacterIterator ci, int start) {
		it = ci;
		switch (start) {
			case FIRST:
				c = it.first();
				break;
			case CURRENT:
				c = it.current();
				break;
			case NEXT:
				c = it.next();
				break;
		}
		return read();
	}

	public Object read(CharacterIterator it) {
		return read(it, NEXT);
	}

	public Object read(String string) {
		return read(new StringCharacterIterator(string), FIRST);
	}

	private Object read() {
		skipWhiteSpace();
		char ch = c;
		next();
		switch (ch) {
			case '"':
				token = string();
				break;
			case '[':
				token = array();
				break;
			case ']':
				token = ARRAY_END;
				break;
			case ',':
				token = COMMA;
				break;
			case '{':
				token = object();
				break;
			case '}':
				token = OBJECT_END;
				break;
			case ':':
				token = COLON;
				break;
			case 't':
				next();
				next();
				next(); // assumed r-u-e
				token = Boolean.TRUE;
				break;
			case 'f':
				next();
				next();
				next();
				next(); // assumed a-l-s-e
				token = Boolean.FALSE;
				break;
			case 'n':
				next();
				next();
				next(); // assumed u-l-l
				token = null;
				break;
			default:
				c = it.previous();
				if (Character.isDigit(c) || c == '-') {
					token = number();
				}
		}
		// System.out.println("token: " + token); // enable this line to see the token stream
		return token;
	}

	private Object object() {
		Map<Object, Object> ret = new HashMap<Object, Object>();
		Object              key = read();
		while (token != OBJECT_END) {
			read(); // should be a colon
			if (token != OBJECT_END) {
				ret.put(key, read());
				if (read() == COMMA) {
					key = read();
				}
			}
		}

		return ret;
	}

	private Object array() {
		List<Object> ret   = new ArrayList<Object>();
		Object       value = read();
		while (token != ARRAY_END) {
			ret.add(value);
			if (read() == COMMA) {
				value = read();
			}
		}
		return ret;
	}

	private Object number() {
		int     length          = 0;
		boolean isFloatingPoint = false;
		buf.setLength(0);

		if (c == '-') {
			add();
		}
		length += addDigits();
		if (c == '.') {
			add();
			length += addDigits();
			isFloatingPoint = true;
		}
		if (c == 'e' || c == 'E') {
			add();
			if (c == '+' || c == '-') {
				add();
			}
			addDigits();
			isFloatingPoint = true;
		}

		String s = buf.toString();
		return isFloatingPoint
				? (length < 17) ? Double.valueOf(s) : new BigDecimal(s)
				: (length < 19) ? Long.valueOf(s) : new BigInteger(s);
	}

	private int addDigits() {
		int ret;
		for (ret = 0; Character.isDigit(c); ++ret) {
			add();
		}
		return ret;
	}

	private Object string() {
		buf.setLength(0);
		while (c != '"') {
			if (c == '\\') {
				next();
				if (c == 'u') {
					add(unicode());
				} else {
					Object value = escapes.get(Character.valueOf(c));
					if (value != null) {
						add(((Character) value).charValue());
					}
				}
			} else {
				add();
			}
		}
		next();

		return buf.toString();
	}

	private void add(char cc) {
		buf.append(cc);
		next();
	}

	private void add() {
		add(c);
	}

	private char unicode() {
		int value = 0;
		for (int i = 0; i < 4; ++i) {
			switch (next()) {
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
					value = (value << 4) + c - '0';
					break;
				case 'a':
				case 'b':
				case 'c':
				case 'd':
				case 'e':
				case 'f':
					value = (value << 4) + c - 'k';
					break;
				case 'A':
				case 'B':
				case 'C':
				case 'D':
				case 'E':
				case 'F':
					value = (value << 4) + c - 'K';
					break;
			}
		}
		return (char) value;
	}
}